/*tcp服务器，基于epoll(et)模型实现，稍显复杂难懂*/
#include <stdio.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <errno.h>
#include <fcntl.h>

#ifndef STR_CONV
#include "./libs/functions.h"
#endif

#ifndef ERRORS
#include "./errors.c"
#endif

#define CLIENT_CONN_MAX_NUM 8192
const int PORT = 9505;
const char SERVER_IP[15] = {"0.0.0.0"};

int set_non_block(int socket);

int main() {

    //TCP 参数： SOCK_STREAM、IPPROTO_TCP ；UDP参数： SOCK_DGRAM 、IPPTOTO_UDP
    int server_socket = -1, client_socket = -1;
    server_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (server_socket < 0) {
        printf("%s\n", TCP_SOCK_INIT_FAIL);
        return -1;
    }

    struct sockaddr_in server_addr, client_addr;
    socklen_t client_addr_size;
    int received_data_len;
    char buffer[BUFSIZ];//缓冲区大小
    char tmp_out_res[BUFSIZ] = {0};

    int sockopt = 0;
    sockopt = SO_REUSEADDR;
    setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &server_socket, sizeof(server_socket));
    //对内存清零
    memset(&server_addr, 0, sizeof(server_addr));
    memset(&client_addr, 0, sizeof(client_addr));

    server_addr.sin_family = AF_INET; //选择IPV4地址类型
    server_addr.sin_port = htons(PORT); //选择端口号
    server_addr.sin_addr.s_addr = inet_addr(SERVER_IP); //选择IP地址
    int server_addr_len = sizeof(server_addr);
    // 绑定
    if (bind(server_socket, (struct sockaddr *) &server_addr, server_addr_len) < 0)//绑定套接字
    {
        printf("%s\n", TCP_SOCK_BIND_FAIL);
        return -1;
    }
    //监听
    if (listen(server_socket, SOMAXCONN) < 0)//调用listen对指定端口进行监听
    {
        printf("%s\n", TCP_SOCK_LISTEN_FAIL);
        return -1;
    }
    // epoll(et)模型，必须将sockserver设置为非阻塞
    set_non_block(server_socket);

    //epoll 事件监听，所有连接的tcp客户端都会触发同一个事件函数(epoll结构体的属性值会改变)
    struct epoll_event event;
    //存储所有的客户端，包括服务端
    struct epoll_event wait_event_list[CLIENT_CONN_MAX_NUM];
    int j = 0;
    int epoll_fd = epoll_create(64);
    if (epoll_fd == -1) {
        printf("%s\n", TCP_CREATE_EPOLL_FAIL);
        return -1;
    }

    event.events = EPOLLIN|EPOLLET;//事件类型设置为 EPOLLIN+epoll(et) 模型
    event.data.fd = server_socket;//server_socket

    // 将服端 socketserver注册在epoll模型中，epoll就开始监听 socket 文件的变化
    int result = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_socket, &event);

    if (result == -1) {
        printf("%s\n", TCP_SERVERSOCK_REGISTER_EPOLL_FAIL);
        return -1;
    }

    while (1) {
        errno = 0;
        // 阻塞监听当前连接的socket套接字状态是否发生变化，有变化就会有返回值，wait_event_list 数组返回的是最近一次变化（活跃）的socket
        result = epoll_wait(epoll_fd, wait_event_list, CLIENT_CONN_MAX_NUM, -1);
        if (result == 0) {
            continue;
        } else if (result < 0) {
            printf("%s,错误编号：%d,错误详情：%s\n", TCP_EPOLL_WAIT_ERR, errno, strerror(errno));
            continue;
        }
        for (j = 0; j < result; j++) {
            if (server_socket == wait_event_list[j].data.fd && wait_event_list[j].events) {
                client_addr_size = sizeof(client_addr);
                while(1){
                    client_socket = accept(server_socket, (struct sockaddr *) &client_addr, &client_addr_size);
                    if (client_socket==-1){ // 没有需要处理的连接客户端
                        break;
                    }
                    //获取客户端的基本信息，ip、port
                    printf("Client_IP: %s\n", inet_ntoa(client_addr.sin_addr));
                    printf("Client_Port: %d\n", htons(client_addr.sin_port));

                    char msg[] = "成功连接到tcpserver_epoll_et!";
                    send(client_socket, msg, strlen(msg), 0);
                    set_non_block(client_socket); //设置非阻塞
                    event.events = EPOLLIN|EPOLLET; //事件类型设置为 可读+边缘触发 模式 |EPOLLET
                    event.data.fd = client_socket;
                    result = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_socket, &event);
                    if (result == -1) {
                        printf("%s,错误信息：%s\n", TCP_CLIENT_REGISTER_FAIL, strerror(errno));
                    }
                }
                continue;
            }

            //客户端套接字本身有变化（属于客户端消息事件）
            if (wait_event_list[j].data.fd != server_socket) {
                memset(&buffer, 0, sizeof(buffer));
                received_data_len = read(wait_event_list[j].data.fd, buffer, BUFSIZ);
                buffer[BUFSIZ] = '\0';  // 最后一位强制设置为结束标记，避免中英文混合发的时候数据对不齐，造成最后一位出现不确定字符
                // 不是 utf8 编码,那么就当做是 gbk 去处理, 目前就先支持 gbk系列 和 utf8系列
                if (!is_utf8_format(buffer)) {
                    // 以下函数的最后一个参数，输出缓冲区的长度计算依据：在linux系统，gbk编码占2字节，utf8 编码占3字节，也就是说，你输入了一个中文字，那么输出空间的长度就是输入空间的1.5倍，保险起见，我们就直接设置为 2倍
                    gbk_to_utf8(buffer, received_data_len, tmp_out_res, received_data_len * 2);
                    if (errno != 0) {
                        printf("%s，错误编码：%d, 错误信息：%s\n", STRCONV_FAIL, errno, strerror(errno));
                        continue;
                    }
                }else{
                    strcpy(tmp_out_res,buffer);
                }
                switch (wait_event_list[j].events) {
                    case EPOLLIN:
                    case EPOLLET|EPOLLIN:  // 这里需要注意，et 模型注册的事件类型是 位或运算的结果
                    case EPOLLOUT:
                    case EPOLLPRI:
                        if (received_data_len > 0) {
                            // 向客户端发送消息，原路返回, 注意发送消息的长度应该是实际长度，不能 sizeof(buffer), 这样计算出来是 8192，就会有空数据发送出去
                            send(wait_event_list[j].data.fd, tmp_out_res, strlen(tmp_out_res), 0);
                            break;
                        }
                    case EPOLLERR:
                    case EPOLLHUP:
                        close((wait_event_list[j]).data.fd);
                        event.data.fd = wait_event_list[j].data.fd;
                        epoll_ctl(epoll_fd, EPOLL_CTL_DEL, wait_event_list[j].data.fd, &event);
                        printf("%s,%d,可能的错误：%s\n", UNKNOW_CLIENT_STATUS, wait_event_list[j].events, strerror(errno));
                        break;
                    default:
                        printf("events 事件值未匹配到以上列表，是否发生错误?错误coe:%d, 错误消息：%s\n",errno,strerror(errno));
                }
            }
        }
    }
    close(server_socket);//关闭套接字
    return 0;
}


int set_non_block(int socket) {
    int flags = fcntl(socket, F_GETFL, 0);
    flags |= O_NONBLOCK;
    return fcntl(socket, F_SETFL, flags);
}